/*******************************************************************************
 * Copyright (c) 2001, 2002 Boris Pruessmann and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Common Public License v0.5
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors:
 *    Boris Pruessmann - Initial implementation.
 ******************************************************************************/
package net.sourceforge.perforce.ui;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.sourceforge.perforce.core.PerforceCorePlugin;
import net.sourceforge.perforce.core.api.IPerforceServer;
import net.sourceforge.perforce.core.resources.LocalFile;
import net.sourceforge.perforce.core.resources.LocalFolder;
import net.sourceforge.perforce.core.resources.LocalResource;
import net.sourceforge.perforce.core.syncinfo.FolderSyncInfo;
import net.sourceforge.perforce.core.syncinfo.ResourceSyncInfo;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.team.core.RepositoryProvider;

/**
  * @version $Revision: 76 $
  * @author  <a href="mailto:bpruessmann@users.sourceforge.net">Boris Pruessmann</a>
  */
class PerforceDecorationRunnable implements Runnable
{
	// --------------------------------------------------------------------------- Private Constants
	
	/** Number for decorate operations that should be bacthed. */
	private static final int NUM_TO_BATCH = 50;
	
	// --------------------------------------------------------------------------- Private Variables
	
	/** Stores a reference to the decoration notifier. */
	private IDecorationNotifier notifier;

	/** ImageDescriptor: Out Of Date. */
	private ImageDescriptor outOfDate = 
		new CachedImageDescriptor(
			PerforceUIPlugin.getPlugin().getImageDescriptor(PerforceUIPlugin.OVR_OUTOFDATE));
			/** ImageDescriptor: has Remote. */
	private ImageDescriptor hasRemote = 
		new CachedImageDescriptor(
			PerforceUIPlugin.getPlugin().getImageDescriptor(PerforceUIPlugin.OVR_HASREMOTE));
	/** ImageDescriptor: Open For Add. */
	private ImageDescriptor openForAdd = 
		new CachedImageDescriptor(
			PerforceUIPlugin.getPlugin().getImageDescriptor(PerforceUIPlugin.OVR_OPENFORADD));
	/** ImageDescriptor: Open For Edit. */
	private ImageDescriptor openForEdit = 
		new CachedImageDescriptor(
			PerforceUIPlugin.getPlugin().getImageDescriptor(PerforceUIPlugin.OVR_OPENFOREDIT));	

	// ------------------------------------------------------------------------- Public Constructors

	/** Constructor for PerforceDecorationRunnable.
	  *
	  * @param notifier the object to notify. 
	  */
	public PerforceDecorationRunnable(IDecorationNotifier notifier)
	{
		this.notifier = notifier;
	}

	// ------------------------------------------------------------------------------ Public Methods

	/** @see Runnable#run() */
	public void run()
	{
		List resources = new ArrayList();
		List decorations = new ArrayList();
		
		while (true)
		{
			try
			{
				IResource resource = notifier.next();
				if (resource == null)
				{
					return;
				}
					
				PerforceDecoration decoration = decorate(resource);
				if (decoration != null)
				{
					resources.add(resource);
					decorations.add(decoration);
					
					if (!resources.isEmpty() && 
					    (notifier.remaining() == 0 || resources.size() >= NUM_TO_BATCH)) 
					{
						notifier.decorated(
							(IResource[]) resources.toArray(new IResource[0]), 
							(PerforceDecoration[]) decorations.toArray(new PerforceDecoration[0]));
						resources.clear();
						decorations.clear();					
					}
				}
			}
			catch (Throwable t)
			{
				PerforceUIPlugin.logError("Unexpected exception.", t);
			}
		}
	}

	// ----------------------------------------------------------------------------- Private Methods

	/** Decorates the given resource.
	  *
	  * @param resource the resource.
	  * @return the decoration. 
	  */
	private PerforceDecoration decorate(IResource resource) 
	{
		RepositoryProvider provider = RepositoryProvider.getProvider(resource.getProject());
		if (!resource.exists() || provider == null || 
		    !provider.getID().equals(PerforceCorePlugin.getTypeId()))
		{
			return null;
		}
		
		IPreferenceStore store = PerforceUIPlugin.getPlugin().getPreferenceStore();
		boolean isDirty = false;		
		boolean computeDeepDirtyCheck = 
			store.getBoolean(PerforceUIPlugin.CALCULATE_DIRTY_PREFERENCE);
		if (resource.getType() == IResource.FILE || computeDeepDirtyCheck)
		{ 
			isDirty = isDirty(resource);
		}							
		
		PerforceDecoration decoration = computeDecoratedText(isDirty, resource);			
		computeOverlays(isDirty, resource, decoration);
		
		return decoration;
	}

	/** Computes the text decorations.
	  *
	  * @param isDirty forced dirty flag.
	  * @param resource the resource.
	  * @return the PerforceDecoratin object. 
	  */
	private PerforceDecoration computeDecoratedText(boolean isDirty, IResource resource)
	{
		IPreferenceStore store = PerforceUIPlugin.getPlugin().getPreferenceStore();
		Map bindings = new HashMap(2);
				
		if (isDirty)
		{
			bindings.put(
				P4DecoratorConfiguration.DIRTY_FLAG, 
				store.getString(IPerforceUIConstants.PREF_DIRTY_FLAG));
		}
					
		if (resource.getType() != IResource.FILE)
		{
			LocalFolder folder = (LocalFolder) LocalResource.createFromResource(resource);
			FolderSyncInfo syncInfo = folder.getFolderSync(); 
			if (syncInfo != null && syncInfo.isAdded())
			{
				bindings.put(
					P4DecoratorConfiguration.DIRTY_FLAG,
					store.getString(IPerforceUIConstants.PREF_ADDED_FLAG));
			}
			
			if (resource.getType() == IResource.PROJECT)
			{
				IPerforceServer server = folder.getServer();
				
				bindings.put(P4DecoratorConfiguration.P4PORT, server.getPort());
				bindings.put(P4DecoratorConfiguration.P4CLIENT, server.getClient());
				bindings.put(P4DecoratorConfiguration.P4USER, server.getUser());
			}
		}
		else
		{
			LocalFile localFile = (LocalFile) LocalResource.createFromResource(resource);
			ResourceSyncInfo resourceSyncInfo = localFile.getSyncInfo();
			if (!resourceSyncInfo.isNull())
			{
				if (resourceSyncInfo.isDeleted())
				{
					return new PerforceDecoration(new HashMap(0), null);
				}
				else
				if (resourceSyncInfo.getAction().equals(ResourceSyncInfo.ACTION_ADD))
				{
					bindings.put(
						P4DecoratorConfiguration.DIRTY_FLAG,
						store.getString(IPerforceUIConstants.PREF_ADDED_FLAG));
				}
				else
				{				
					bindings.put(
						P4DecoratorConfiguration.HAVE_REV, 
						'#' + resourceSyncInfo.getHaveRev().toString());		
									
					bindings.put(
						P4DecoratorConfiguration.HEAD_REV, 
						'#' + resourceSyncInfo.getHeadRev().toString());
				}
			}
		}
		
		return new PerforceDecoration(bindings, null);		
	}
	
	/** Computes the graphical decorations.
	  *
	  * @param isDirty forced dirty flag.
	  * @param resource the resource.
	  * @param decoration the decoration object. 
	  */
	private void computeOverlays(boolean isDirty, IResource resource, PerforceDecoration decoration)
	{
		List result = new ArrayList();				
		switch (resource.getType())
		{
			case IResource.PROJECT:
			case IResource.FOLDER:	
				LocalResource localResource = LocalResource.createFromResource(resource);
				{
					if (localResource.hasRemote())
					{
						result.add(hasRemote);
					}		
				}		
				break;
				
			case IResource.FILE:
				{
					LocalFile localFile = (LocalFile) LocalResource.createFromResource(resource);
					
					if (localFile.hasRemote())
					{
						if (localFile.isOutOfDate())
						{
							result.add(outOfDate);
						}
						else
						{
							result.add(hasRemote);
						}
					}
						
					ResourceSyncInfo resourceSyncInfo = localFile.getSyncInfo();
					if (!resourceSyncInfo.isNull())
					{
						if (resourceSyncInfo.isDeleted())
						{
							result = new ArrayList();
						}
						else
						if (resourceSyncInfo.getAction().equals(ResourceSyncInfo.ACTION_ADD))
						{
							result.add(openForAdd);
						}
						else
						if (resourceSyncInfo.getAction().equals(ResourceSyncInfo.ACTION_EDIT))
						{
							result.add(openForEdit);
						}
					}
				}
				break;
		}
		
		decoration.setOverlays(result);
	}

	/** Calucaltes the dirty state of the given resource. 
	  *
	  * @param resource the resource. 
	  * @return true if resource is dirty, false otherwise.
	  */
	private boolean isDirty(IResource resource)
	{
		final CoreException DECORATOR_EXCEPTION =
			new CoreException(new Status(IStatus.OK, "id", 1, "", null));//$NON-NLS-1$ //$NON-NLS-2$
		try
		{
			resource.accept(new IResourceVisitor()
			{
				public boolean visit(IResource resource) throws CoreException
				{

					// a project can't be dirty, continue with its children
					if (resource.getType() == IResource.PROJECT)
					{
						return true;
					}

					// if the resource does not exist in the workbench or on the 
					// file system, stop searching.
					if (!resource.exists())
					{
						return false;
					}
						
					LocalResource localResource = LocalResource.createFromResource(resource);
					if (localResource.isIgnored())
					{
						return false;
					}

					if (localResource instanceof LocalFile)
					{
						if (localResource.isDirty())
						{
							throw DECORATOR_EXCEPTION;
						}
					}

					return true;
				}
			}, IResource.DEPTH_INFINITE, true);
		}
		catch (CoreException e)
		{
			//if our exception was caught, we know there's a dirty child
			return e == DECORATOR_EXCEPTION;
		}
		
		return false;
	}

	// ---------------------------------------------------------------------- Private Nested Classes
	
	/** Define a cached image descriptor which only creates the image data once */
	private static class CachedImageDescriptor extends ImageDescriptor 
	{
		// ----------------------------------------------------------------------- Private Variables
		
		/** Stores the ImageDescriptor. */
		private ImageDescriptor descriptor;
		/** Stores the ImageData. */
		private ImageData data;
		
		// --------------------------------------------------------------------- Public Constructors
		
		/** Constructor for CachedImageDescriptor.
		  *
		  * @param descriptor the image descriptor. 
		  */
		public CachedImageDescriptor(ImageDescriptor descriptor) 
		{
			this.descriptor = descriptor;
		}
		
		// -------------------------------------------------------------------------- Public Methods
		
		/** Called to retrieve the object's ImageData.
		  *
		  * @return the ImageData. 
		  */
		public ImageData getImageData() 
		{
			if (data == null) 
			{
				data = descriptor.getImageData();
			}

			return data;
		}
	}
}
